feat(hooks): add Claude Code pre-compact snapshot hook#226
Conversation
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
📝 Walkthrough
WalkthroughThis PR adds a new Pre-Compact hook that captures bounded Gradata context snapshots. The hook integrates into the Claude Code adapter's lifecycle management, writing deterministic JSON snapshots before compaction. Command construction helpers, snapshot schema and persistence logic, adapter install/uninstall support, and comprehensive validation tests are all included. ChangesPre-Compact Hook Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 OpenGrep (1.22.0)OpenGrep fatal error (exit code 2): �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Gradata/src/gradata/hooks/pre_compact.py`:
- Around line 118-121: The code only checks brain_dir.exists() before
proceeding, which still allows a file path to pass and later fail when creating
snapshot directories; update the guard to ensure brain_dir is a directory (use
brain_dir.is_dir()) and return None if it's not a directory, then proceed to
call _session_id(data), _build_snapshot(brain_dir, data), and
_write_snapshot(_snapshot_path(brain_dir, session_id), ...) only when
brain_dir.is_dir() is True to prevent write errors.
In `@Gradata/tests/test_hook_adapters.py`:
- Around line 88-89: The two asserts check fragments separately and can match
different entries; change them to assert that a single command string contains
both fragments by replacing the two asserts with one that uses any(...) over
commands, e.g. any("BRAIN_DIR=" in cmd and "gradata.hooks.pre_compact" in cmd
for cmd in commands), so update the assertions in test_hook_adapters.py to
ensure both substrings appear in the same command entry.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1ef769ee-2ad4-47b9-a0cb-2474b89f0d15
📒 Files selected for processing (6)
Gradata/src/gradata/hooks/adapters/_base.pyGradata/src/gradata/hooks/adapters/claude_code.pyGradata/src/gradata/hooks/pre_compact.pyGradata/tests/test_hook_adapters.pyGradata/tests/test_hooks_intelligence.pyGradata/tests/test_pre_compact_hook.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: pytest (py3.11)
- GitHub Check: pytest windows-latest / py3.12
- GitHub Check: pytest ubuntu-latest / py3.11
- GitHub Check: pytest macos-latest / py3.11
- GitHub Check: pytest windows-latest / py3.11
- GitHub Check: pytest ubuntu-latest / py3.12
- GitHub Check: pytest macos-latest / py3.12
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/tests/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/tests/**/*.py: SetBRAIN_DIRenvironment variable viatmp_pathin conftest.py for test isolation — ensure_paths.pymodule cache refreshes when callingBrain.init()directly inside tests
Add unit tests intests/test_*.pyfor every CI push without LLM calls (deterministic); mark integration tests with@pytest.mark.integrationand skip them by default (they hit real LLM APIs)
Files:
Gradata/tests/test_hook_adapters.pyGradata/tests/test_hooks_intelligence.pyGradata/tests/test_pre_compact_hook.py
Gradata/src/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/src/**/*.py: Prefersentence-transformersfor local embeddings,google-genaifor Gemini embeddings,cryptographyfor AES-GCM encrypted system.db,bm25sfor BM25 rule ranking, andmem0aifor external memory adapters — guard all optional dependency imports withtry / except ImportErrorat the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bareexcept: pass— use typed exceptions or at minimumlogger.warning(...)withexc_info=Trueto avoid silent failure in a memory product
Never import from out-of-scope sibling directories../Sprites/or../Hausgem/withingradata/*code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to../Sprites/,../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from insidegradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes
Files:
Gradata/src/gradata/hooks/adapters/_base.pyGradata/src/gradata/hooks/adapters/claude_code.pyGradata/src/gradata/hooks/pre_compact.py
🔇 Additional comments (6)
Gradata/src/gradata/hooks/adapters/_base.py (1)
139-157: LGTM!Gradata/src/gradata/hooks/pre_compact.py (1)
22-117: LGTM!Also applies to: 122-123
Gradata/src/gradata/hooks/adapters/claude_code.py (1)
15-18: LGTM!Also applies to: 62-97, 120-137
Gradata/tests/test_hook_adapters.py (1)
3-3: LGTM!Also applies to: 69-87
Gradata/tests/test_hooks_intelligence.py (1)
281-313: LGTM!Gradata/tests/test_pre_compact_hook.py (1)
10-73: LGTM!
| if not brain_dir.exists(): | ||
| return None | ||
| session_id = _session_id(data) | ||
| _write_snapshot(_snapshot_path(brain_dir, session_id), _build_snapshot(brain_dir, data)) |
There was a problem hiding this comment.
Guard against file-path BRAIN_DIR before writing snapshots.
Line 118 only checks existence. If BRAIN_DIR points to a file, Line 121 can fail during snapshot directory creation.
Suggested fix
def main(data: dict[str, Any]) -> None:
resolved = resolve_brain_dir()
if not resolved:
return None
brain_dir = Path(resolved)
- if not brain_dir.exists():
+ if not brain_dir.is_dir():
return None
session_id = _session_id(data)
_write_snapshot(_snapshot_path(brain_dir, session_id), _build_snapshot(brain_dir, data))
return None📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if not brain_dir.exists(): | |
| return None | |
| session_id = _session_id(data) | |
| _write_snapshot(_snapshot_path(brain_dir, session_id), _build_snapshot(brain_dir, data)) | |
| if not brain_dir.is_dir(): | |
| return None | |
| session_id = _session_id(data) | |
| _write_snapshot(_snapshot_path(brain_dir, session_id), _build_snapshot(brain_dir, data)) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Gradata/src/gradata/hooks/pre_compact.py` around lines 118 - 121, The code
only checks brain_dir.exists() before proceeding, which still allows a file path
to pass and later fail when creating snapshot directories; update the guard to
ensure brain_dir is a directory (use brain_dir.is_dir()) and return None if it's
not a directory, then proceed to call _session_id(data),
_build_snapshot(brain_dir, data), and _write_snapshot(_snapshot_path(brain_dir,
session_id), ...) only when brain_dir.is_dir() is True to prevent write errors.
| assert any("BRAIN_DIR=" in command for command in commands) | ||
| assert any("gradata.hooks.pre_compact" in command for command in commands) |
There was a problem hiding this comment.
Assert both fragments on the same command entry.
Line 88 and Line 89 can pass even if BRAIN_DIR= and gradata.hooks.pre_compact appear in different commands.
Suggested fix
- assert any("BRAIN_DIR=" in command for command in commands)
- assert any("gradata.hooks.pre_compact" in command for command in commands)
+ assert any(
+ "BRAIN_DIR=" in command and "gradata.hooks.pre_compact" in command
+ for command in commands
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert any("BRAIN_DIR=" in command for command in commands) | |
| assert any("gradata.hooks.pre_compact" in command for command in commands) | |
| assert any( | |
| "BRAIN_DIR=" in command and "gradata.hooks.pre_compact" in command | |
| for command in commands | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Gradata/tests/test_hook_adapters.py` around lines 88 - 89, The two asserts
check fragments separately and can match different entries; change them to
assert that a single command string contains both fragments by replacing the two
asserts with one that uses any(...) over commands, e.g. any("BRAIN_DIR=" in cmd
and "gradata.hooks.pre_compact" in cmd for cmd in commands), so update the
assertions in test_hook_adapters.py to ensure both substrings appear in the same
command entry.
Summary
python -m gradata.hooks.pre_compact<brain>/.precompact-snapshots/<session-id>.jsongradata install --agent claude-codeto install PreCompact alongside PreToolUseVerification
python3 -m pytest tests/test_pre_compact_hook.py tests/test_hook_adapters.py::test_claude_code_install_writes_pre_compact_entry tests/test_hooks_intelligence.py::test_pre_compact_saves_snapshot tests/test_hooks_intelligence.py::test_pre_compact_no_brain -q— 7 passedpython3 -m pytest tests/test_hook_adapters.py tests/test_pre_compact_hook.py tests/test_hooks_intelligence.py tests/test_jit_inject.py tests/test_session_history.py -q— 87 passed, 1 skippedCloses GRA-1210. Parent epic: GRA-1198 / GH #206.